/*
 * ParameterDeployer.java July 2009
 *
 * Copyright (C) 2009, Quirino Zagarese <quirino@laszloitalia.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 */
package org.eu4ria.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;

import javassist.bytecode.annotation.Annotation;

import org.eu4ria.annotations.ArrayParameter;
import org.eu4ria.annotations.BooleanParameter;
import org.eu4ria.annotations.DateParameter;
import org.eu4ria.annotations.DoubleParameter;
import org.eu4ria.annotations.FloatParameter;
import org.eu4ria.annotations.IntParameter;
import org.eu4ria.annotations.LongParameter;
import org.eu4ria.annotations.StringParameter;
import org.eu4ria.core.annotations.ArrayParameterDeployer;
import org.eu4ria.core.annotations.BooleanParameterDeployer;
import org.eu4ria.core.annotations.DateParameterDeployer;
import org.eu4ria.core.annotations.DoubleParameterDeployer;
import org.eu4ria.core.annotations.FloatParameterDeployer;
import org.eu4ria.core.annotations.IntParameterDeployer;
import org.eu4ria.core.annotations.LongParameterDeployer;
import org.eu4ria.core.annotations.StringParameterDeployer;
import org.eu4ria.exceptions.IncompatibleAnnotationException;
import org.eu4ria.parameters.ParameterValidator;

public class ParameterDeployer {

	private static ParameterDeployer instance;

	private Hashtable<String, Vector<String>> parameterBindings = new Hashtable<String, Vector<String>>();

	private Hashtable<String, String> typesToAnnotationsBindings = new Hashtable<String, String>();

	private Hashtable<String, Object> annotationsDeployers = new Hashtable<String, Object>();

	private ParameterDeployer() {

	}

	public static ParameterDeployer getInstance() {
		if (instance == null) {
			instance = new ParameterDeployer();
			setup();
		}
		return instance;
	}

	private static void setup() {
		Vector<Class> bindings = new Vector<Class>();
		bindings.add(String.class);
		instance.addParameterBinding(StringParameter.class, bindings,
				new StringParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(Integer.class);
		bindings.add(int.class);
		instance.addParameterBinding(IntParameter.class, bindings,
				new IntParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(Long.class);
		bindings.add(long.class);
		instance.addParameterBinding(LongParameter.class, bindings,
				new LongParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(Float.class);
		bindings.add(float.class);
		instance.addParameterBinding(FloatParameter.class, bindings,
				new FloatParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(Double.class);
		bindings.add(double.class);
		instance.addParameterBinding(DoubleParameter.class, bindings,
				new DoubleParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(Date.class);
		bindings.add(java.sql.Date.class);
		bindings.add(Time.class);
		bindings.add(Timestamp.class);
		instance.addParameterBinding(DateParameter.class, bindings,
				new DateParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(Boolean.class);
		bindings.add(boolean.class);
		instance.addParameterBinding(BooleanParameter.class, bindings,
				new BooleanParameterDeployer());
		bindings = new Vector<Class>();
		bindings.add(int[].class);
		bindings.add(Integer[].class);
		bindings.add(long[].class);
		bindings.add(Long[].class);
		bindings.add(float[].class);
		bindings.add(Float[].class);
		bindings.add(double[].class);
		bindings.add(Double[].class);
		bindings.add(boolean[].class);
		bindings.add(Boolean[].class);
		bindings.add(String[].class);
		instance.addParameterBinding(ArrayParameter.class, bindings,
				new ArrayParameterDeployer());

	}

	public boolean isTypeSupported(String type) {
		return typesToAnnotationsBindings.containsKey(type);
	}

	public boolean isAnnotationSupported(Annotation annotation) {
		return parameterBindings.containsKey(annotation.getTypeName());
	}

	public ParameterValidator deployParameter(Class class1,
			Annotation annotation) throws IncompatibleAnnotationException {
		ParameterValidator parameter = null;
		String annotationName = null;
		if (annotation != null) {
			annotationName = annotation.getTypeName();
			if (!checkParameterType(class1, annotation)) {
				throw new IncompatibleAnnotationException(
						"Can not deploy parameters of type "
								+ class1.getSimpleName() + ", using the "
								+ annotationName + " annotation.");
			}
		} else {
			annotationName = typesToAnnotationsBindings.get(class1.getName());
		}
		Class[] parameterTypes = new Class[1];
		parameterTypes[0] = Annotation.class;
		Method m = null;
		Object target = annotationsDeployers.get(annotationName);
		try {
			m = target.getClass().getMethod("deploy", parameterTypes);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
		Object[] params = new Object[1];
		params[0] = annotation;
		try {
			parameter = (ParameterValidator) m.invoke(target, params);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		parameter.setJavaType(class1);
		return parameter;
	}

	private boolean checkParameterType(Class class1, Annotation annotation) {
		Vector<String> types = parameterBindings.get(annotation.getTypeName());
		for (String type : types) {
			if (class1.getName().equals(type)) {
				return true;
			}
		}
		return false;
	}

	public void addParameterBinding(Class annotation, Vector<Class> bindings,
			Object deployer) {
		annotationsDeployers.put(annotation.getName(), deployer);
		Vector<String> bindingsNames = new Vector<String>();
		for (Class type : bindings) {
			String typeName = type.getName();
			typesToAnnotationsBindings.put(typeName, annotation.getName());
			bindingsNames.addElement(typeName);
		}
		parameterBindings.put(annotation.getName(), bindingsNames);
	}

}
